package de.rj.urwalking.data;

import java.io.File;
import java.io.FileFilter;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

//@author mka; used to do xml
import org.simpleframework.xml.Element;
import org.simpleframework.xml.ElementList;
import org.simpleframework.xml.Root;
import org.simpleframework.xml.Serializer;
import org.simpleframework.xml.core.Persister;
import org.simpleframework.xml.stream.Format;

import android.os.Environment;
import android.util.Log;

import de.rj.urwalking.config.Config;
import de.rj.urwalking.config.Constants;
import de.rj.urwalking.data.survey.SalientObjectSurvey;
import de.rj.urwalking.db.ReadWritePostgreSQL;
import de.rj.urwalking.util.ExperimentManager;
import de.rj.urwalking.util.Util;

/**
 * 
 * An Experiment consists of a user walking a specific route at a specific date. It may contain several (completed)
 * surveys. GPS and sensor data is logged throughout the experiment.
 * 
 * @author rjj
 * 
 */

@Root(name = "urw_participant_experiment")
public class Experiment {

	public static final int TYPE_INDOOR = 1;
	public static final int TYPE_OUTDOOR = 2;

	@Element(name = "refparticipant")
	private int participantId;

	private String routeDescription;
	@Element(name = "begin")
	private long startTimestamp;
	@Element(name = "end")
	private long endTimestamp;
	private String logFilename;
	@Element(name = "changeofviewallowed")
	private boolean allowViewChange;
	@Element(name = "initialview")
	private int initialView;

	@Element(name = "devicename")
	private String strDevice;
	@Element(name = "deviceid")
	private String streDeviceId;
	@Element(name = "androidversion")
	private String strAndroidVersion;
	@Element(name = "urwalkingversion")
	private String strUrWalkingVersion;

	@ElementList
	private ArrayList<ViewChange> viewChanges = new ArrayList<ViewChange>();
	@ElementList
	private ArrayList<SalientObjectSurvey> surveys = new ArrayList<SalientObjectSurvey>();
	private int type;

	private static final String TAG = Config.TAG;
	@Element(name = "refexperiment")
	private int id;

	public Experiment(int participantId, int initialView, boolean allowViewChange, int type, String av, String dev,
			int id, String v, String devid) {
		startTimestamp = System.currentTimeMillis();
		this.participantId = participantId;
		this.allowViewChange = allowViewChange;
		this.initialView = initialView;
		this.type = type;
		this.strAndroidVersion = av;
		this.strDevice = dev;
		this.id = id;
		this.strUrWalkingVersion = v;
		this.streDeviceId = devid;
		Log.i(TAG, Experiment.class.getName() + " Hi, I'm running Urwalking " + this.strUrWalkingVersion
				+ " on device " + this.strDevice + " having id " + this.streDeviceId + " having installed Android "
				+ this.strAndroidVersion);
	}

	public boolean isViewChangeAllowed() {
		return allowViewChange;
	}

	public int getParticipantId() {
		return participantId;
	}

	public void setParticipantId(int participantId) {
		this.participantId = participantId;
	}

	public String getRouteDescription() {
		return routeDescription;
	}

	public void setRouteDescription(String routeDescription) {
		this.routeDescription = routeDescription;
	}

	public long getStartTimestamp() {
		return startTimestamp;
	}

	public long getEndTimestamp() {
		return endTimestamp;
	}

	public ArrayList<SalientObjectSurvey> getSurveys() {
		return surveys;
	}

	public void addSalientObjectSurvey(SalientObjectSurvey salientObjectSurvey) {
		surveys.add(salientObjectSurvey);
	}

	public boolean hasPreviousObject() {
		return surveys.size() > 0;
	}

	/**
	 * Generates a name for the log file based on the current timestamp.
	 * 
	 * @return the file name for the log file
	 */
	public String generateFilename() {
		logFilename = "log-" + this.startTimestamp + "-" + this.getParticipantId() + "-sensordata" + ".csv";
		return logFilename;
	}

	/**
	 * Used to write the XML file on the sdcard
	 * 
	 * @param xmlFilename
	 *            the filename of the xml file to be generated for current experiment.
	 * @author mka
	 */
	public boolean saveResultsXML(boolean isFinished, int extra) {
		boolean blnRes = false;
		endTimestamp = System.currentTimeMillis();

		String fn = generateXmlFileName(isFinished, extra);
		// ensure xml headline
		Serializer serializer = new Persister(new Format("<?xml version=\"1.0\" encoding= \"UTF-8\" ?>"));
		Log.i(TAG, Experiment.class.getName() + ": Try to initialize file " + fn);
		File dirXml = new File(Environment.getExternalStorageDirectory(), Config.FOLDER_NAME);
		File xmlFile;
		if (dirXml.exists() && dirXml.canWrite()) {
			xmlFile = new File(dirXml, fn);
		} else {
			dirXml.mkdirs();
			xmlFile = new File(dirXml, fn);
		}

		Log.i(TAG,
				Experiment.class.getName() + ": Location of XML file containing experiment data: "
						+ xmlFile.getAbsolutePath());

		try {
			serializer.write(this, xmlFile);
			if (xmlFile.exists()) {
				Log.i(TAG, Experiment.class.getName() + ": Successfully stored experiment data as XML file on sdcard: "
						+ xmlFile.getAbsolutePath());
				cleanUpMessyDirectory(isFinished);
				blnRes = true;
			} else
				blnRes = false;

		} catch (Exception e) {
			Log.e(TAG, Experiment.class.getName() + ": Failed to store experiment data as XML+" + " file on sdcard: "
					+ e.getMessage());

			if (xmlFile.exists()) {
				Util.deleteFile(xmlFile);
				blnRes = false;
			}

			// in case exception occurred during the save of current experiment
			// as a whole
			if (isFinished) {
				// try to keep as much surveys as possible
				boolean keptLatest = keepLatest();
				if (keptLatest) {
					Log.e(TAG, Experiment.class.getName() + ": Successfully determined the+"
							+ " file containing the maximum+" + " number of surveys before experiment was finished.");
					blnRes = true;
				} else {
					Log.e(TAG, Experiment.class.getName() + ": Unable to determine the latest+"
							+ " file containing the+" + " maximum number of surveys available for experiment "
							+ this.startTimestamp);
					blnRes = false;
				}
			}
		}
		return blnRes;
	}

	/**
	 * Searches the directory Config.FOLDER_NAME for the highest survey count available and renames the file to final;
	 * to be called in case save failed once experiment was finished. surveys.size() is not used in order to avoid side
	 * effects.
	 * 
	 * @return true if and only if the file could be determined and renamed successfully.
	 * @author mka
	 */
	private boolean keepLatest() {
		Log.i(TAG, Experiment.class.getName() + ": Starting to try to keep as much surveys as possible for experiment "
				+ this.startTimestamp);
		boolean blnRes = false;

		File dirXml = new File(Environment.getExternalStorageDirectory(), Config.FOLDER_NAME);
		File[] xmlFilesExp = dirXml.listFiles();

		final List<Integer> intList = new ArrayList<Integer>();

		for (int i = 0; i < xmlFilesExp.length; i++) {
			if (xmlFilesExp[i].isFile()) {
				String fn = xmlFilesExp[i].getName();

				if (fn.startsWith("experimentdata-" + this.startTimestamp) && fn.endsWith(".xml")) {

					int start = fn.indexOf("-", fn.lastIndexOf("-"));
					int end = fn.indexOf(".xml");

					intList.add(Integer.parseInt(fn.substring(start + 1, end)));
				}
			}
		}

		if (intList.size() > 0) {
			Collections.sort(intList); // ascending order ->
										// intList.get(intList.size()-1)
			Log.i(TAG, Experiment.class.getName() + ": Determined " + intList.get(intList.size() - 1)
					+ " as maximum number of surveys before experiment was finished.");
			boolean blnRenameSuccessful = doRename(intList.get(intList.size() - 1), dirXml);
			if (blnRenameSuccessful) {
				Log.i(TAG, Experiment.class.getName() + ": Successfully renamed file.");
				blnRes = true;
				cleanUpMessyDirectory(true);
			} else {
				Log.i(TAG, Experiment.class.getName() + ": Failed to rename file.");
				blnRes = false;
			}
		} else {
			Log.i(TAG, Experiment.class.getName() + ": Unable to determine file containing+"
					+ "the maximum number of surveys before experiment was finished.");
			blnRes = false;
		}

		return blnRes;
	}

	/**
	 * A highly specialized method used to rename a file like experimentdata-123456-X.xml to
	 * experimentdata-123456-final.xml
	 * 
	 * @param num
	 *            ::= maximum number of surveys found to preserve
	 * @param dirXml
	 *            ::= the storage directory of xml files
	 * @return true if, and only if, the file was renamed, successfully.
	 */
	private final boolean doRename(int num, File dirXml) {
		boolean blnRenameSuccessful = false;
		// do renaming
		String tmp = generateXmlFileName(false, num);

		File original = new File(dirXml, tmp);
		File newName = new File(dirXml, generateXmlFileName(true, -1000));

		blnRenameSuccessful = original.renameTo(newName);
		return blnRenameSuccessful;
	}

	/**
	 * Delete any XML files not named *final.xml
	 * 
	 * @return TRUE if and only if one or more files were deleted, successfully.
	 * @author mka
	 */
	private boolean cleanUpMessyDirectory(boolean isFinished) {
		Log.i(TAG, Experiment.class.getName() + ": Start to cleanup directory for experiment " + this.startTimestamp);
		boolean blnRes = false;
		File dirXml = new File(Environment.getExternalStorageDirectory(), Config.FOLDER_NAME);

		if (isFinished) {
			File[] xmlFilesExp = dirXml.listFiles(new FileFilter() {
				@Override
				public boolean accept(File pathname) {
					// files only
					if (pathname.isFile() == false) {
						return false;
					}
					return (pathname.getName().startsWith("experimentdata-" + startTimestamp)
							&& pathname.getName().endsWith(".xml") && !pathname.getName().contains("final"));

				}
			});
			for (File f : xmlFilesExp)
				blnRes = Util.deleteFile(f);
		} else {
			// vorhergehendes l�schen
			Log.i(TAG, "Name of previous file is: " + generateXmlFileName(false, getSurveys().size() - 1));
			File[] prevFile = dirXml.listFiles(new FileFilter() {
				@Override
				public boolean accept(File pathname) {
					// files only
					if (pathname.isFile() == false) {
						return false;
					}
					// previous file
					return ((pathname.getName().equals(generateXmlFileName(false, getSurveys().size() - 1))));
				}
			});

			if (prevFile.length == 0) {
				Log.i(TAG, Experiment.class.getName() + ": Did not find a previous file.");
				return false;
			} else if (prevFile.length > 1) {
				Log.i(TAG, Experiment.class.getName() + ": Eventually found more than one previous files.");
				return false;
			} else
				blnRes = Util.deleteFile(prevFile[0]);
		}

		return blnRes;
	}

	/**
	 * Create a filename for XML data to be saved on sd card
	 * 
	 * @param isFinal
	 *            true if and only if a filename ending using "-final" ought to be constructed
	 * @param extra
	 *            -1000 in case this.surveys.size() shall be used
	 * @return the filename as String
	 */
	private String generateXmlFileName(boolean isFinal, int extra) {
		if (isFinal)
			return "experimentdata-" + this.startTimestamp + "-" + this.getParticipantId() + "-final.xml";
		else if (extra == -1000)
			return "experimentdata-" + this.startTimestamp + "-" + this.getParticipantId() + "-" + this.surveys.size()
					+ ".xml";
		else
			return "experimentdata-" + this.startTimestamp + "-" + this.getParticipantId() + "-" + extra + ".xml";

	}

	/**
	 * Called whenever the user changes the view from street to above or vice versa.
	 * 
	 * @param viewType
	 *            the new view type: 0 = from above, 1 = street
	 */

	public void addViewChange(int perspective) {

		int viewType = 0;
		// map from new Perspective constants to previously used ones:
		if (perspective == Perspective.TYPE_3D_ABOVE) {
			viewType = 0;
		} else if (perspective == Perspective.TYPE_3D_STREET) {
			viewType = 1;
		}

		viewChanges.add(new ViewChange(viewType));
	}

	public int getType() {
		return type;
	}

}
